#include "image.h"
#include <iostream>
#include <fstream>

using namespace std;

int Image::fromBMPtoNN(char* fileIn, char* fileOut, int _color, int hidden_neurons, int max_seconds, int max_iter, int min_snr, int start_eta, int verboose)
{	
	int i,j;

	// Deschid BMP-ul de input
	if(OpenInputBMP(_color, fileIn, verboose))
		return 1;

	ofstream out (fileOut, ios::out|ios::binary);
	if(!out)
	{
		cout<<"ERROR: Cannot open output file "<<fileOut<<" ... \n";
		return 1;
	}

	// Scriu fisierul NN
	int output_neurons=64;

	out.put('s');
	out.put('u');
	out.put('n');
	out.write((const char*)&h,sizeof(int));
	out.write((const char*)&w,sizeof(int));
	out.write((const char*)&hidden_neurons,sizeof(int));
	out.write((const char*)&output_neurons,sizeof(int));
	out.put('s');
	out.put('u');
	out.put('n');

	if(!_color)
	{
		// Deschid reteaua neurala si fac training cu ea
		if(!allocatedCurrent)
			current=new NeuralNetwork(64,hidden_neurons);
		current->TrainigSample(samples,h*w,64);
		current->Train(max_iter,max_seconds,min_snr,start_eta,verboose);
		allocatedCurrent=1;

		out.put('0');

		for(i=0;i<output_neurons;i++)
			out.write((const char*)(current->GetOutputWeights()[i]),(hidden_neurons+1)*sizeof(double));

		out.put('s');
		out.put('u');
		out.put('n');
		
		UWORD temp;

		for(i=0;i<h*w; i++)
			for(j=0; j<hidden_neurons; j++)
			{
				temp=(UWORD)(255*255*current->GetHiddenOutputs(i)[j]);
				out.write((char*)&temp,sizeof(UWORD));
			}

		out.put('s');
		out.put('u');
		out.put('n');

		out.close();
	
		return 0;
	}
	else
	{
		// Deschid reteaua neurala si fac training cu ea
		if(!allocatedCurrentR)
			currentR=new NeuralNetwork(64,hidden_neurons);
		currentR->TrainigSample(samplesR,h*w,64);
		currentR->Train(max_iter/3,max_seconds/3,min_snr,start_eta,verboose);
		if(!allocatedCurrentG)
			currentG=new NeuralNetwork(64,hidden_neurons);
		currentG->TrainigSample(samplesG,h*w,64);
		currentG->Train(max_iter/3,max_seconds/3,min_snr,start_eta,verboose);
		if(!allocatedCurrentB)
			currentB=new NeuralNetwork(64,hidden_neurons);
		currentB->TrainigSample(samplesB,h*w,64);
		currentB->Train(max_iter/3,max_seconds/3,min_snr,start_eta,verboose);
		
		allocatedCurrentR=1;
		allocatedCurrentG=1;
		allocatedCurrentB=1;

		out.put('1');

		for(i=0;i<output_neurons;i++)
			out.write((const char*)(currentR->GetOutputWeights()[i]),(hidden_neurons+1)*sizeof(double));
		for(i=0;i<output_neurons;i++)
			out.write((const char*)(currentG->GetOutputWeights()[i]),(hidden_neurons+1)*sizeof(double));
		for(i=0;i<output_neurons;i++)
			out.write((const char*)(currentB->GetOutputWeights()[i]),(hidden_neurons+1)*sizeof(double));

		out.put('s');
		out.put('u');
		out.put('n');
		
		UWORD temp;

		for(i=0;i<h*w; i++)
			for(j=0; j<hidden_neurons; j++)
			{
				temp=(UWORD)(255*255*currentR->GetHiddenOutputs(i)[j]);
				out.write((char*)&temp,sizeof(UWORD));
				temp=(UWORD)(255*255*currentG->GetHiddenOutputs(i)[j]);
				out.write((char*)&temp,sizeof(UWORD));
				temp=(UWORD)(255*255*currentB->GetHiddenOutputs(i)[j]);
				out.write((char*)&temp,sizeof(UWORD));
			}

		out.put('s');
		out.put('u');
		out.put('n');

		out.close();

		return 0;
	}
}

int Image::fromNNtoBMP(char* fileIn, char* fileOut, int verboose)
{
	int i;

	i=OpenInputNN(fileIn, fileOut, verboose);

	if(i==1)
	{
		cout<<"ERROR: Invalid input neural network file "<<fileIn<<" ... \n";
		return 1;
	}

	if(i==0)
		return OpenOutputBMP(0,fileOut,verboose);

	if(i==-1)
		return OpenOutputBMP(1,fileOut,verboose);

	return 0;
}

int Image::OpenInputNN(char* fileIn, char* fileOut, int verboose)
{
	int i;
	char crt;
	UWORD* hidden_out;

	int color;
	int hidden_neurons;
	int output_neurons;


	ifstream in (fileIn, ios::in|ios::binary);
	if(!in)
		return 1;

	in.get(crt); if((crt!='s')||!in) return 1;
	in.get(crt); if((crt!='u')||!in) return 1;
	in.get(crt); if((crt!='n')||!in) return 1;

	in.read((char*)&h,sizeof(int));
	in.read((char*)&w,sizeof(int));
	in.read((char*)&hidden_neurons,sizeof(int));
	in.read((char*)&output_neurons,sizeof(int));

	in.get(crt); if((crt!='s')||!in) return 1;
	in.get(crt); if((crt!='u')||!in) return 1;
	in.get(crt); if((crt!='n')||!in) return 1;
	in.get(crt); if(!in) return 1; // Momentan nu merge cu culori :(

	if(crt=='0')
	{
		color=0;

		// We have enough information to create the neural network
		if(!allocatedCurrent) 
			current=new NeuralNetwork(output_neurons,hidden_neurons);
		allocatedCurrent=1;

		for(i=0; i<output_neurons; i++)
		{
			in.read((char*)(current->GetOutputWeights()[i]),(hidden_neurons+1)*sizeof(double));
			if(!in)
				return 1;
		}


		in.get(crt); if((crt!='s')||!in) return 1;
		in.get(crt); if((crt!='u')||!in) return 1;
		in.get(crt); if((crt!='n')||!in) return 1;


		//pentru debug:
		if(verboose)
		{
			cout<<"\nNeural Network File Loading Statistics: ";
			cout<<"\ncolor:          no";
			cout<<"\nheight:         "<<h*8;
			cout<<"\nwidth:          "<<w*8;
			cout<<"\nhidden neurons: "<<hidden_neurons;
			cout<<"\noutput neurons: "<<output_neurons<<"\n";
		}

		hidden_out=new UWORD[hidden_neurons];

		// Creez noua structura in care se tin rezultatele
		results=new double* [h*w];

		for (i = 0; i < h*w; i++)
		{
			results[i] = new double[64];
		}
		allocatedResults=1;

		for(i=0; i<h*w; i++)
		{
			in.read((char*)hidden_out,hidden_neurons*sizeof(UWORD));
			current->GetPartialResults(hidden_out, results[i]);
			if(!in)
				return 1;
		}
	}
	else
	{
		color=1;

		// We have enough information to create the neural network
		if(!allocatedCurrentR)	currentR=new NeuralNetwork(output_neurons,hidden_neurons);
		if(!allocatedCurrentG)  currentG=new NeuralNetwork(output_neurons,hidden_neurons);
		if(!allocatedCurrentB)  currentB=new NeuralNetwork(output_neurons,hidden_neurons);
		allocatedCurrentR=1;
		allocatedCurrentG=1;
		allocatedCurrentB=1;

		for(i=0; i<output_neurons; i++)
		{
			in.read((char*)(currentR->GetOutputWeights()[i]),(hidden_neurons+1)*sizeof(double));
			if(!in)
				return 1;
			in.read((char*)(currentG->GetOutputWeights()[i]),(hidden_neurons+1)*sizeof(double));
			if(!in)
				return 1;
			in.read((char*)(currentB->GetOutputWeights()[i]),(hidden_neurons+1)*sizeof(double));
			if(!in)
				return 1;
		}


		in.get(crt); if((crt!='s')||!in) return 1;
		in.get(crt); if((crt!='u')||!in) return 1;
		in.get(crt); if((crt!='n')||!in) return 1;


		//pentru debug:
		if(verboose)
		{
			cout<<"\nNeural Network File Loading Statistics: ";
			cout<<"\ncolor:          yes";
			cout<<"\nheight:         "<<h*8;
			cout<<"\nwidth:          "<<w*8;
			cout<<"\nhidden neurons: "<<hidden_neurons;
			cout<<"\noutput neurons: "<<output_neurons<<"\n";
		}

		hidden_out=new UWORD[hidden_neurons];

		// Creez noua structura in care se tin rezultatele
		resultsR=new double* [h*w];
		resultsG=new double* [h*w];
		resultsB=new double* [h*w];

		for (i = 0; i < h*w; i++)
		{
			resultsR[i] = new double[64];
			resultsG[i] = new double[64];
			resultsB[i] = new double[64];
		}
		allocatedResultsR=1;
		allocatedResultsG=1;
		allocatedResultsB=1;

		for(i=0; i<h*w; i++)
		{
			in.read((char*)hidden_out,hidden_neurons*sizeof(UWORD));
			currentR->GetPartialResults(hidden_out, resultsR[i]);
			in.read((char*)hidden_out,hidden_neurons*sizeof(UWORD));
			currentG->GetPartialResults(hidden_out, resultsG[i]);
			in.read((char*)hidden_out,hidden_neurons*sizeof(UWORD));
			currentB->GetPartialResults(hidden_out, resultsB[i]);
			if(!in)
				return 1;
		}
	}
	in.get(crt); if((crt!='s')||!in) return 1;
	in.get(crt); if((crt!='u')||!in) return 1;
	in.get(crt); if((crt!='n')||!in) return 1;

	delete hidden_out;

	return -color;
}

int Image::OpenInputBMP(int color, char* inputFile, int verboose)
{	
	ebmpBYTE r,g,b;
	int i,j,k,l;

	if(verboose)
		cout<<"Opening file "<<inputFile<<" ... \n";
	
	if(fileBMP.ReadFromFile(inputFile))
	{
		h=fileBMP.TellHeight()/8;
		w=fileBMP.TellWidth()/8;

		if((h*8!=fileBMP.TellHeight())||(w*8!=fileBMP.TellWidth()))
				if(verboose)
					cout<<"WARINING: fileBMP file size is not 8-pixel multiple. Image will be truncated.";

		if(color)
		{
			// creez structura de samples...
			if(!allocatedSamplesR)
				samplesR=new double* [h*w];
			allocatedSamplesR=1;

			if(!allocatedSamplesB)
				samplesB=new double* [h*w];
			allocatedSamplesB=1;

			if(!allocatedSamplesG)
				samplesG=new double* [h*w];
			allocatedSamplesG=1;

			// umplu structura de samples...
			for (i = 0; i < h; i++)
				for (j = 0; j < w; j++)
				{
					samplesR[i * w + j] = new double[64];
					samplesG[i * w + j] = new double[64];
					samplesB[i * w + j] = new double[64];
					for (k = 0; k < 8; k++)
						for (l = 0; l < 8; l++)
						{
							r=fileBMP.GetPixel(j * 8 + l,i * 8 + k).Red;
							g=fileBMP.GetPixel(j * 8 + l,i * 8 + k).Green;
							b=fileBMP.GetPixel(j * 8 + l,i * 8 + k).Blue;
							samplesR[i * w + j][8 * k + l] = ((double)r) / 255;
							samplesG[i * w + j][8 * k + l] = ((double)g) / 255;
							samplesB[i * w + j][8 * k + l] = ((double)b) / 255;
						}
				}
				return 0;

		}
		else
		{
			// creez structura de samples...
			if(!allocatedSamples)
				samples=new double* [h*w];
				allocatedSamples=1;

			// umplu structura de samples...
			for (i = 0; i < h; i++)
				for (j = 0; j < w; j++)
				{
					samples[i * w + j] = new double[64];
					for (k = 0; k < 8; k++)
						for (l = 0; l < 8; l++)
						{
							r=fileBMP.GetPixel(j * 8 + l,i * 8 + k).Red;
							g=fileBMP.GetPixel(j * 8 + l,i * 8 + k).Green;
							b=fileBMP.GetPixel(j * 8 + l,i * 8 + k).Blue;
							samples[i * w + j][8 * k + l] = ((double)r+(double)g+(double)b) / 3 / 255;
						}
				}
			return 0;
		}
	}
	else
	{
		cout<<"ERROR: Cannot open input file "<<inputFile<<" ... \n";
		return 1;
	}
}


int Image::OpenOutputBMP(int color, char* outputFile,int verboose)
{
	ebmpBYTE r,g,b;
	RGBApixel c;
	int i,j,k,l;

	if(verboose)
		cout<<"Preparing contents of new file "<<outputFile<<" ... \n";

	fileBMP.SetSize(w*8, h*8);

	if(!color)
	{
		fileBMP.SetBitDepth(8);
		CreateGrayscaleColorTable(fileBMP);
	}
	else
		fileBMP.SetBitDepth(24);

    for (i = 0; i < h; i++)
        for (j = 0; j < w; j++)
            for (k = 0; k < 8; k++)
                for (l = 0; l < 8; l++)
                {
					if(color)
					{
						r = (ebmpBYTE)((resultsR[i * w + j][8 * k + l] * 255));
						g = (ebmpBYTE)((resultsG[i * w + j][8 * k + l] * 255));
						b = (ebmpBYTE)((resultsB[i * w + j][8 * k + l] * 255));
						c.Red=r;
						c.Green=g;
						c.Blue=b;
						fileBMP.SetPixel(8 * j + l, 8 * i + k, c);
					}
					else
					{
						g = (ebmpBYTE)((results[i * w + j][8 * k + l] * 255));
						c.Red=g;
						c.Green=g;
						c.Blue=g;
						fileBMP.SetPixel(8 * j + l, 8 * i + k, c);
					}
                }
	
	cout<<"Opening file "<<outputFile<<" ... \n";
	if(fileBMP.WriteToFile(outputFile))
	{
		return 0;
	}
	else
	{
		cout<<"ERROR: Cannot open output file "<<outputFile<<" ... \n";
		return 1;
	}
}

Image::Image()
{
	allocatedSamples=0;
	allocatedSamplesR=0;
	allocatedSamplesG=0;
	allocatedSamplesB=0;
	allocatedResults=0;
	allocatedResultsR=0;
	allocatedResultsG=0;
	allocatedResultsB=0;
	allocatedCurrent=0;
	allocatedCurrentR=0;
	allocatedCurrentG=0;
	allocatedCurrentB=0;
}

Image::~Image()
{
	int i;

	if(allocatedSamples)
	{
		for(i=0; i<h*w; i++)
			delete[] samples[i];
		delete[] samples;
	}

	if(allocatedSamplesR)
	{
		for(i=0; i<h*w; i++)
			delete[] samplesR[i];
		delete[] samplesR;
	}

	if(allocatedSamplesG)
	{
		for(i=0; i<h*w; i++)
			delete[] samplesG[i];
		delete[] samplesG;
	}

	if(allocatedSamplesB)
	{
		for(i=0; i<h*w; i++)
			delete[] samplesB[i];
		delete[] samplesB;
	}

	if(allocatedResults)
	{
		for(i=0; i<h*w; i++)
			delete[] results[i];
		delete[] results;
	}

	if(allocatedResultsR)
	{
		for(i=0; i<h*w; i++)
			delete[] resultsR[i];
		delete[] resultsR;
	}

	if(allocatedResultsG)
	{
		for(i=0; i<h*w; i++)
			delete[] resultsG[i];
		delete[] resultsG;
	}

	if(allocatedResultsB)
	{
		for(i=0; i<h*w; i++)
			delete[] resultsB[i];
		delete[] resultsB;
	}

	if(allocatedCurrent)
		delete current;

	if(allocatedCurrentB)
		delete currentB;

	if(allocatedCurrentG)
		delete currentG;

	if(allocatedCurrentR)
		delete currentR;
}
